using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Content;
using System.Collections.ObjectModel;
using CoolEngine.Engine.Input;
using System.Diagnostics.CodeAnalysis;
using CoolEngine.Gui;
using CoolEngine.Engine.Scripting;
using System.Text;
using Microsoft.Scripting;
using Microsoft.Scripting.Runtime;
namespace CoolEngine.Engine
{
    public struct FormattedText
    {
        public string Text { get; set; }
        [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Colour", Justification = "Learn to spell.")]
        public Color Colour { get; set; }
    }

    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    [ContainsBoundCommands]
    public class ConsoleWindow : GameScreen
    {
        private static List<FormattedText> transcript = new List<FormattedText>();
        private static ReadOnlyCollection<FormattedText> readOnlyTranscript = new ReadOnlyCollection<FormattedText>(transcript);
        public static ReadOnlyCollection<FormattedText> Transcript { get { return readOnlyTranscript; } }
        private static readonly string[] newLine = new string[] { Environment.NewLine, "\n" };
        private StringBuilder builder = new StringBuilder();
        private static ConsoleWindow instance;
        public static ConsoleWindow Instance { get { return instance; } }
        public int LinesToShow { get; set; }
        public float FontSize { get; set; }
        public SpriteFont Font { get; set; }
        public int LinesFromEnd { get; set; }
        private uint cursorPosition;
        //private uint leftInputTextChar;
        private bool cursorShowing;
        public Color BackgroundColour { get; set; }
        /// <summary>
        /// Gets or sets the foreground colour for the console.
        /// </summary>
        /// <value>The foreground colour.</value>
        [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Colour", Justification = "Learn to spell.")]
        public static Color ForegroundColour { get; set; }
        /// <summary>
        /// Writes a line to the console.
        /// </summary>
        /// <param name="input">The input.</param>
        public static void WriteLine(string input)
        {
            Write(input + Environment.NewLine, ForegroundColour);
        }
        /// <summary>
        /// Writes a line to the console.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="colour">The colour.</param>
        [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "colour", Justification = "Learn to spell.")]
        public static void WriteLine(string input, Color colour)
        {
            Write(input + Environment.NewLine, colour);
        }
        /// <summary>
        /// Writes the input to the console, without a new line.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="colour">The colour.</param>
        [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "colour", Justification = "Learn to spell.")]
        public static void Write(string input, Color colour)
        {
            string[] split = input.Split(newLine, StringSplitOptions.RemoveEmptyEntries);
            foreach (string line in split)
            {
                transcript.Add(new FormattedText() { Colour = colour, Text = line.Replace(Environment.NewLine, "") });
            }
        }
        /// <summary>
        /// Writes the input to the console, without a new line.
        /// </summary>
        /// <param name="input">The input.</param>
        public static void Write(string input)
        {
            Write(input, ForegroundColour);
        }

        public ConsoleWindow(CoolGame game, GuiRenderer renderer)
            : base(game, renderer, GameMenuControl.Instance)
        {
            instance = this;
            game.Services.AddService(typeof(ConsoleWindow), this);
            ForegroundColour = Color.White;
            BackgroundColour = Color.Black;
            DrawOrder = Int32.MaxValue;
            Variables.RegisterControl("console", this);
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            base.Initialize();
            MouseVisible = true;
            Enabled = false;
            Visible = false;
            Font = Game.Content.Load<SpriteFont>("Fonts\\Calibri");
            DrawLocation = new Rectangle(0, 0, Game.Graphics.PreferredBackBufferWidth, Game.Graphics.PreferredBackBufferHeight / 2);//new Rectangle(0, 0, Parent.DrawLocation.Width, Parent.DrawLocation.Height / 2);
            LinesToShow = (int)(DrawLocation.Height / Font.MeasureString("test").Y) - 1;
            FullScreen = false;            
        }
        /// <summary>
        /// Called when the Console needs to be drawn.
        /// </summary>
        /// <param name="gameTime">Time passed since the last call to Microsoft.Xna.Framework.DrawableGameComponent.Draw(Microsoft.Xna.Framework.GameTime).</param>
        public override void Draw(GameTime gameTime)
        {
            int lines = Math.Min(LinesToShow, transcript.Count);
            Game.GraphicsDevice.Clear(BackgroundColour);
            for (int i = 0; i < lines; i++)
            {
                FormattedText line = transcript[transcript.Count - lines + i - LinesFromEnd];
                Vector2 size = Font.MeasureString(line.Text);
                Batch.DrawString(Font, line.Text, TopLeft + new Vector2(5, 5 + i * size.Y), line.Colour);
            }
            if (builder.Length > 0)
            {
                string text = builder.ToString();
                Vector2 size = Font.MeasureString(text);
                Batch.DrawString(Font, text, TopLeft + new Vector2(5, DrawLocation.Height - size.Y - 5), Color.White);
            }
            base.Draw(gameTime);
        }
        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            cursorShowing = ((int)gameTime.TotalGameTime.TotalSeconds * 2) % 2 == 0;

            base.Update(gameTime);
        }
        public override bool CaptureKeyboard
        {
            get
            {
                return true;
            }
        }
        /// <summary>
        /// Appends either a upper or lowercase letter, depending on if shift is down.
        /// </summary>
        /// <param name="shift">if set to <c>true</c> [shift].</param>
        /// <param name="cap">The uppercase letter.</param>
        /// <param name="lower">The lowercase letter.</param>
        private void AppendShifted(bool shift, char cap, char lower)
        {
            builder.Insert((int)cursorPosition, shift ? cap : lower);
            cursorPosition++;
        }
        /// <summary>
        /// Appends either a upper or lowercase string, depending on if shift is down.
        /// </summary>
        /// <param name="shift">if set to <c>true</c> [shift].</param>
        /// <param name="cap">The uppercase string.</param>
        /// <param name="lower">The lowercase string.</param>
        private void AppendShifted(bool shift, string cap, string lower)
        {
            string text = shift ? cap : lower;
            builder.Insert((int)cursorPosition, text);
            cursorPosition += (uint)text.Length;
        }
        private void Append(string text)
        {
            builder.Insert((int)cursorPosition, text);
            cursorPosition += (uint)text.Length;
        }
        private void Append(char text)
        {
            builder.Insert((int)cursorPosition, text);
            cursorPosition++;
        }
        public override void OnKeyboardInput(KeyboardInputEventArgs e)
        {
            bool shift = (e.State.IsKeyDown(Keys.LeftShift) || e.State.IsKeyDown(Keys.RightShift)) ^ e.State.IsKeyDown(Keys.CapsLock);
            foreach (Keys key in e.State.GetPressedKeys().Except(e.OldState.GetPressedKeys()))
            {
                switch (key)
                {
                    case Keys.LeftShift:
                    case Keys.RightShift:
                    case Keys.RightControl:
                    case Keys.RightAlt:
                    case Keys.LeftAlt:
                    case Keys.LeftControl:
                    case Keys.LeftWindows:
                    case Keys.RightWindows:
                        break;
                    case Keys.D0:
                        AppendShifted(shift, ')', '0');
                        break;
                    case Keys.D1:
                        AppendShifted(shift, '!', '1');
                        break;
                    case Keys.D2:
                        AppendShifted(shift, '@', '2');
                        break;
                    case Keys.D3:
                        AppendShifted(shift, '#', '3');
                        break;
                    case Keys.D4:
                        AppendShifted(shift, '$', '4');
                        break;
                    case Keys.D5:
                        AppendShifted(shift, '%', '5');
                        break;
                    case Keys.D6:
                        AppendShifted(shift, '^', '6');
                        break;
                    case Keys.D7:
                        AppendShifted(shift, '&', '7');
                        break;
                    case Keys.D8:
                        AppendShifted(shift, '*', '8');
                        break;
                    case Keys.D9:
                        AppendShifted(shift, '(', '9');
                        break;
                    case Keys.Enter:
                        string text = builder.ToString();
                        WriteLine(text);
                        try
                        {
                            CommandParser.RunCommand(text);
                        }
                        catch (SyntaxErrorException)
                        {
                            WriteLine("'" +text.Tokenise()[0] + "': command not found", Color.Red);
                        }
                        catch (UnboundNameException)
                        {
                            WriteLine("'" + text.Tokenise()[0] + "': command not found", Color.Red);
                        }
                        ClearInput();
                        break;
                    case Keys.Left:
                        if (cursorPosition != 0)
                            cursorPosition--;
                        break;
                    case Keys.Right:
                        cursorPosition++;
                        break;
                    case Keys.OemMinus:
                        AppendShifted(shift, '_', '-');
                        break;
                    case Keys.OemPlus:
                        AppendShifted(shift, '+', '=');
                        break;
                    case Keys.Home:
                        cursorPosition = 0;
                        break;
                    case Keys.End:
                        cursorPosition = (uint)builder.Length;
                        break;
                    case Keys.Space:
                        Append(' ');
                        break;
                    case Keys.OemSemicolon:
                        AppendShifted(shift, ':', ';');
                        break;
                    case Keys.OemComma:
                        AppendShifted(shift, '<', ',');
                        break;
                    case Keys.OemQuotes:
                        AppendShifted(shift, '"', '\'');
                        break;
                    case Keys.OemPeriod:
                        AppendShifted(shift, '>', '.');
                        break;
                    case Keys.OemQuestion:
                        AppendShifted(shift, '?', '/');
                        break;
                    case Keys.OemPipe:
                        AppendShifted(shift, '|', '\\');
                        break;
                    case Keys.OemOpenBrackets:
                        AppendShifted(shift, '{', '[');
                        break;
                    case Keys.OemCloseBrackets:
                        AppendShifted(shift, '}', ']');
                        break;
                    case Keys.OemTilde:
                        if (shift)
                        {
                            Append('~');
                            break;
                        }
                        ToggleConsole();
                        break;
                    case Keys.Back:
                        if (cursorPosition > 0)
                        {
                            builder.Remove((int)(cursorPosition - 1), 1);
                            cursorPosition--;
                        }
                        break;

                    case Keys.Q:
                    case Keys.W:
                    case Keys.E:
                    case Keys.R:
                    case Keys.T:
                    case Keys.Y:
                    case Keys.U:
                    case Keys.I:
                    case Keys.O:
                    case Keys.P:
                    case Keys.A:
                    case Keys.S:
                    case Keys.D:
                    case Keys.F:
                    case Keys.G:
                    case Keys.H:
                    case Keys.J:
                    case Keys.K:
                    case Keys.L:
                    case Keys.Z:
                    case Keys.X:
                    case Keys.C:
                    case Keys.V:
                    case Keys.B:
                    case Keys.N:
                    case Keys.M:
                        string keyName = key.ToString();
                        AppendShifted(shift, keyName[0], keyName.ToLower()[0]);
                        break;
                    default:
                        break;
                }
            }
            base.OnKeyboardInput(e);
        }
        /// <summary>
        /// Toggles the console.
        /// </summary>
        /// <param name="ignored">Neccessary for an Action`1 delegate.</param>
        [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "ignored", Justification = "Needed to fit the delegate.")]
        [DefaultBoundKey(Keys.OemTilde, ButtonPressStage.Down, "console")]
        [Command(Command = "Toggle", Description = "Toggles the console", Name = "ToggleConsole", Help = "", Module = "Console")]
        public static string ToggleConsole(params string[] ignored)
        {
            instance.Visible = !instance.Visible;
            instance.Enabled = !instance.Enabled;
            instance.HasFocus = !instance.HasFocus;
            instance.ClearInput();
            ConsoleWindow.WriteLine("Toggled console");
            return null;
        }

        private void ClearInput()
        {
            builder.Length = 0;
            cursorPosition = 0;
        }
    }
}